服务端装饰器
NestJS 微服务提供了一组专用装饰器来处理消息的接收和上下文访问。
@Payload() -- 提取消息数据
@Payload() 装饰器用于从接收到的消息中提取数据,类似于 HTTP 控制器中的 @Body() 装饰器。
基本用法 -- 接收完整 payload:
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';
@Controller()
export class MathController {
@MessagePattern({ cmd: 'sum' })
accumulate(@Payload() data: number[]): number {
return (data || []).reduce((a, b) => a + b, 0);
}
}
typescript
提取 payload 中的特定属性:
当客户端发送的数据被包裹在一个对象中时,可以指定提取具体的属性:
@MessagePattern({ cmd: 'sum' })
accumulate(@Payload('numbers') data: number[]): number {
return data.reduce((a, b) => a + b, 0);
}
// 客户端发送: { numbers: [1, 2, 3] }
// data 接收到: [1, 2, 3]
typescript
@Payload() 还可以接收 Pipe 参数进行数据验证和转换:
@MessagePattern({ cmd: 'create-user' })
createUser(@Payload(new ValidationPipe()) createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
typescript
@Ctx() -- 获取上下文信息
@Ctx() 装饰器用于获取当前消息的上下文对象,包含连接的元数据和环境信息。
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload, Ctx, TcpContext } from '@nestjs/microservices';
@Controller()
export class MathController {
@MessagePattern({ cmd: 'sum' })
accumulate(@Payload() data: number[], @Ctx() context: TcpContext): number {
console.log('上下文信息:', context);
// 打印结果包含:
// - pattern: { cmd: 'sum' } 对应的 message pattern
// - 传输层类型: TCP context 对象
// - 底层连接信息: socket 连接状态等
return data.reduce((a, b) => a + b, 0);
}
}
typescript
使用场景:
- 访问底层传输细节 -- 如 TCP 连接状态、数据缓冲区
- 增强控制逻辑 -- 根据连接状态做出更准确的决策(如连接是否新创建、是否已断开)
- 调试和监控 -- 记录请求的传输层信息
注意:当同时使用 @Payload() 和 @Ctx() 时,必须显式添加 @Payload() 装饰器,否则第一个参数会被当作完整的消息对象。
客户端装饰器
@Client() -- 直接实例化客户端(不推荐)
@Client() 装饰器提供了一种在 Service 或 Controller 中直接创建微服务客户端实例的方式:
import { Injectable } from '@nestjs/common';
import { Client, Transport, ClientProxy } from '@nestjs/microservices';
@Injectable()
export class AppService {
@Client({ transport: Transport.TCP })
private client: ClientProxy;
getSum() {
return this.client.send<number>({ cmd: 'sum' }, [1, 2, 3]);
}
}
typescript
为什么不推荐使用:
NestJS 官方明确指出这种方式难以测试和共享实例。原因如下:
- 每个 Controller 或 Service 使用
@Client()会创建独立的客户端实例 - 多个 Controller 会产生多个实例,浪费资源
- 实例的生命周期不受 DI 容器管理,难以进行单元测试
推荐方式 -- ClientsModule 依赖注入
// app.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
@Module({
imports: [
ClientsModule.register([
{
name: 'MATH_SERVICE',
transport: Transport.TCP,
},
]),
],
})
export class AppModule {}
// app.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
@Injectable()
export class AppService {
constructor(
@Inject('MATH_SERVICE') private readonly client: ClientProxy,
) {}
getSum() {
return this.client.send<number>({ cmd: 'sum' }, [1, 2, 3]);
}
}
typescript
通过 ClientsModule.register() 注册的客户端实例由 NestJS 的 DI 系统统一管理,可以在多个 Controller 和 Service 之间共享,也更容易进行单元测试。
异步消息处理
微服务的消息处理天然支持异步模式,使用方式与普通的 async/await 一致:
服务端异步处理:
@MessagePattern({ cmd: 'getUser' })
async getUser(@Payload() id: number): Promise<User> {
return await this.userService.findById(id);
}
typescript
客户端异步调用:
async fetchUser(id: number): Promise<User> {
return await this.client.send<User>({ cmd: 'getUser' }, id).toPromise();
}
typescript
这种异步模式与编写普通的 Promise 方法完全一致,微服务框架自动处理了底层的消息传递和序列化。
↑